iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
AI 自動化

情報進來,內容發出去:30 天拆一條每天真的在跑的 AI 內容產線系列 第 12

Day 12|不知道是誰,不用整條重跑:具名分支怎麼暫停再繼續?

  • 分享至 

  • xImage
  •  

第 11 天結尾,具名引言和待辦負責人停在 needs-human

停是停了。但我回頭看那個標籤,它其實什麼都沒交代:誰來看?看完把答案寫在哪?下次程序啟動時,怎麼知道有人回過、回的是哪一版?

needs-human 如果只是一個字串,人工交接就還藏在工作流外面。今天補的就是這一段:第一個程序把問題存下來、關掉;第二個程序讀回答案,只把停住的那條分支跑完。匿名報告不用陪著等。

先接上前幾天

第 7 天到第 11 天,其實一直在補同一條鏈:

天數 新增能力 留給下一天的產物
7 分清一次任務、固定工作流與自動化 自動化契約
8 輸入不合格時安全停止 輸入契約
9 每一站都能交付、拒收與回查 交接紀錄
10 整理後文字回到來源時間窗 可回放逐字稿包
11 分開匿名分群、身分綁定與下游權限 身分候選與受限輸出
12 只暫停具名分支並跨程序續跑 複核請求、決定與分支狀態

前面五天畫的都是狀態和契約。今天是第一次階段驗收:不只畫,要真的讓第一次程序停下,第二次程序接著跑。

跟第 11 天的分工也要說清楚。第 11 天用負向測試證明匿名標記不能直接變姓名,也建立了摘要與過期判定。今天沿用這些結論,不重講;今天處理的是第 11 天沒碰的兩題:未確認時怎麼等,人回覆後怎麼繼續。

全文只回答一個問題:

身分需要人判斷時,工作流怎麼讓匿名報告先完成,只暫停具名分支,等另一個程序讀回同一來源版本的決定再續跑?

不比較人工介入框架,不做完整會議工具。今天交付的是一個最小、可執行、也可以失敗的分支狀態機。下面講。

先看結果:程序真的關掉了,第二個程序接上

用生活的方式想:外場把點單釘在廚房窗口,就去忙別桌,不會站在窗口等。廚師看完在單子上寫答案,釘回去。下一個來拿單的外場可能已經換班,他不需要知道前一個人在想什麼,照單子做就好。

這次的合成範例就是這樣跑的:

第一個 Node.js 程序執行 start,得到 waiting_for_human。它把四份檔案逐檔原子替換寫進暫存目錄:current-source.jsonanonymous-report.jsonreview-request.jsonstate.json,加上一份執行軌跡。然後結束。

測試再啟動第二個 Node.js 程序執行 resume。它讀回 current-source.jsonreview-decision.jsonstate.json,得到 completed_named_metadata。兩次程序識別碼不同,測試每次都會另啟兩個程序並斷言兩者不一樣。

這比在同一個函式裡「等一個立刻回傳的模擬答案」多驗了一層:記憶體已經消失,續跑只能靠保存下來的狀態和決定。

它沒驗的也要先講:單一寫入者、作業系統暫存目錄、沒有並行、沒有程序崩潰時的跨檔交易、沒有遠端佇列。只證明兩個不同程序能用 JSON 檔接續。

人工交接不是一個 approved,是三份分開的資料

老實說,最容易的寫法是一個 approved: true。它壞在後面:核准的是什麼?適用哪一版?重送過沒有?解鎖了哪一項能力?一個布林值答不了這四題。

所以拆成三份:

資料 誰建立 必須保存什麼 不能取代什麼
review-request.json 工作流 範圍、分支、來源版本、候選、允許決定、能力上限 人的實際決定
review-decision.json 複核端 唯一決定識別碼、請求版本、來源綁定、選項 登入、授權與內容真實性
state.json 工作流 各分支狀態、待處理請求、已處理決定、事件序列 正式資料庫的交易與鎖

三份都是本文的最小資料形狀,不是任何框架或雲端服務的官方格式。

複核請求:先把範圍、來源、能力上限寫死

{
  "review_request_id": "review-run-synthetic-day12-001-v1",
  "request_version": 1,
  "run_id": "run-synthetic-day12-001",
  "review_scope": "speaker_identity",
  "branch_id": "named_output",
  "source_artifact": {
    "artifact_id": "transcript-package-synthetic-day12",
    "version": "sha256:f7cb93a5…b3f2"
  },
  "allowed_decisions": [
    "confirm_identity",
    "keep_anonymous",
    "reject_candidate"
  ],
  "capability_ceiling": ["write:named_speaker_metadata"]
}

請求先界定:只能判斷 speaker_identity,只作用於 named_output,來源是哪一版摘要,人只能在三個選項裡選。capability_ceiling 是天花板:就算複核端回傳更多權限,工作流也不能超過它。

所有識別碼、人物參照和摘要都來自明示合成的固定資料,沒有真實姓名、音訊或逐字稿。

人工決定:也要綁回同一請求與同一來源版本

{
  "decision_id": "decision-fixture-confirm-v1",
  "review_request_id": "review-run-synthetic-day12-001-v1",
  "request_version": 1,
  "review_scope": "speaker_identity",
  "branch_id": "named_output",
  "source_artifact": {
    "artifact_id": "transcript-package-synthetic-day12",
    "version": "sha256:f7cb93a5…b3f2"
  },
  "choice": "confirm_identity",
  "confirmed_candidate_id": "candidate-speaker-00-person-alpha",
  "reviewer_id": "reviewer_synthetic_fixture"
}

續跑前逐一比對:請求識別碼、請求版本、複核範圍、分支、來源摘要、候選。少一項對不上,就不續跑。決定資料也不能自己夾帶 publish:content 這種新能力。

reviewer_id 在這裡只是合成的稽核欄位。登入、權限、真人身分,都不在這次測試範圍。

等待的是具名工作單元,不是所有輸出

分支怎麼切,是今天最重要的設計決定。

分支 第一次程序結束前 人工決定後
anonymous_report completed,已保存 anonymous-report.json 保持完成,不因具名決定而重算
named_output waiting_for_human 依決定成為 completed_named_metadatacompleted_anonymous_onlystopped_rejected

共同前處理成功後,匿名報告就有自己的完成條件。身分不確定只擋需要真實身分的那條,不能反過來抹掉已經完成的匿名輸出。

這裡有個容易寫過頭的地方。我查了 OpenAI Agents SDK 的人工介入介面,它是整個執行範圍的:需要核准的工具呼叫變成中斷項目,呼叫端拿到狀態後核准或拒絕,再繼續整個執行。這不能被寫成「框架天然只暫停具名分支」。

後來發現正確的分工是:框架負責保存與恢復的機制;哪個分支可以先完成,是我們在框架外面先切工作單元才得到的。匿名和具名要先設計成獨立單元,暫停才會只停一邊。

三種決定,各自只改具名分支

決定 具名分支結果 新增能力 明確禁止
confirm_identity completed_named_metadata write:named_speaker_metadata 具名引言、建立待辦、發布
keep_anonymous completed_anonymous_only write:anonymous_speaker_metadata 具名中繼資料與所有外部動作
reject_candidate stopped_rejected 不得改用另一個名字猜測

三條路都保留已完成的匿名報告。

confirm_identity 只產生說話者標記與人物參照的中繼資料,不含顯示名稱、引言或待辦。這是最小權限的直接套用:NIST 對最小權限的描述是,只授予使用者或程序完成被指派工作所需的最低權限。speaker_identity 這個決定被指派的工作,只有寫具名說話者中繼資料。具名引言還要文字忠實與公開授權;建立待辦要另一份行動批准;發布是更後面的獨立權限。

reject_candidate 那一列的禁止項要特別看:拒絕候選之後,程式不能自己換一個名字再猜。拒絕就是拒絕。

正常路徑不夠,至少要把六種重播弄壞

一條人工交接會不會壞,不是看它順跑一次,是看它被重送、換版、改內容時會不會做錯事。

變異 實跑結果 狀態是否被錯改
相同 decision_id、相同內容重送 duplicate_noop 否,序列化狀態完全不變
來源已換版才送回舊決定 stale_reissued 舊請求過期,新版重新派件
相同 decision_id 改成另一個選項 decision_id_reuse_conflict 否,拒絕套用
複核範圍或分支不符 review_scope_mismatchbranch_id_mismatch
確認了另一個候選 candidate_mismatch
決定資料夾帶額外能力 decision_capability_injection

另外還驗了兩件事:已結案的請求不能用新的 decision_id 重開;狀態轉移不修改輸入物件。這批自動測試我這次跑是 14 條全過。

原因碼只屬於本篇範例,不是框架或標準規定的共同錯誤碼。

每一列背後都有一個外部世界的理由,逐條講:

重送要能安全忽略。 外部事件常常是至少送達一次。Azure Durable Task 的文件就明說重複事件可能出現,要去重就在事件資料裡放唯一識別碼。這正是 decision_id 的工作。

但冪等不是看到識別碼重複就吞掉。 AWS Builders' Library 特別處理同一個識別碼被拿來表達不同意圖的風險。所以這裡同時保存 decision_id 和決定內容摘要:相同識別碼、相同內容,回同一個結果;相同識別碼、內容不同,明確衝突,不猜哪一份算數。

來源換版,舊決定要失效。 AWS Step Functions 的回呼模式把任務權杖交給外部工作者,逾時之後會產生新權杖,舊的不再有效。HTTP 的 If-Match 也是同一個原則:目前版本和指定版本不符就不套用變更。本文採一樣的失敗關閉:決定綁的來源摘要和目前來源不同,舊請求標成 stale,為新版重新派一件 v2,具名分支繼續等。

範圍、分支、候選任一不符,都不續跑。 這三項在請求裡已經寫死,決定回來對不上,就是另一件事的答案被送錯地方。

能力只能收窄,不能被決定放大。 決定資料夾帶 publish:content,工作流照 capability_ceiling 擋掉。

重播不是「再跑一次看看」。它是:對同一個意圖得出同一個結果,對不同意圖明確衝突。

對照官方機制:哪一段是框架給的,哪一段要自己做

我沒有用任何一個框架實作這個範例,但它們的文件把責任邊界講得很清楚,值得對照。

Microsoft Agent Framework 用明確的請求與回覆處理人工介入:執行器送出請求,工作流暫停,呼叫端提供回覆後再繼續。文件也說,若建立檢查點,待處理請求會隨狀態保存,恢復時重新發出。這支持「人工交接做成資料,不是口頭訊息」。

同一套文件區分兩種儲存:記憶體儲存適合同一程序內恢復;檔案式儲存才能在程序重啟後讀回檢查點,而且恢復時工作流拓樸與執行器識別要相容。所以本文不用「標成等待人工」推論持久化已完成,而是讓第一個程序真的寫檔、結束,第二個程序讀檔驗證。

OpenAI Agents SDK 的 RunState 提供把執行狀態轉成字串、再從字串恢復的介面,長時間等待不必維持同一個記憶體程序。但可以序列化,不等於已經安全保存。放哪裡、誰能讀、版本相不相容、恢復哪個業務分支,都還是應用程式的責任。

三份文件的共同點:框架處理「停下來、存起來、接回去」。至於停哪一條、存什麼欄位、接回去時要驗哪些對應,是我們的事。

稽核與摘要的邊界

來源包、複核請求、複核決定、具名中繼資料,在本文裡是四個不同實體;建立請求和套用決定是兩個不同活動。W3C PROV-DM 用實體、活動、代理者描述資料怎麼產生、被誰用、歸誰。分開記之後,才回答得了「哪一版來源被哪個決定改變了哪個分支」,而不是只看到最後一份輸出。

摘要的部分沿用第 11 天:先遞迴排序 JSON 物件鍵,再算 SHA-256,避免只是鍵順序不同就被判成來源換版。這個簡化函式只服務固定合成資料,不是完整的 RFC 8785 實作。

SHA-256 能辨認位元有沒有變。摘要不同,資料變了;摘要相同,只說明輸入表示相同。它不能證明候選姓名正確、複核者是真人,或這個決定已獲組織授權。

這次證明了什麼,沒證明什麼

證明了:

1/ 匿名分支可以在第一個程序裡完成,不用陪具名分支等人。

2/ 第一個程序結束後,第二個程序只靠檔案就能續跑具名分支。

3/ 三種決定各自只改具名分支,能力不超過請求寫死的天花板。

4/ 六種重播變異都不會錯改狀態。

沒證明的:

▸ 多寫入者、並行、程序崩潰時的跨檔交易。這是本機單一寫入者範例,不是正式交易系統。

▸ 真實複核者介面、登入、授權。reviewer_id 只是合成欄位。

▸ 任何語音模型準確率或真實會議證據。第 11 天和今天都只用合成資料驗控制層。

下一篇:行動候選不沿用身分批准

今天只把 speaker_identity 做成能暫停與續跑的分支。

Day 13 要新增的是行動候選:逐字稿裡有人提到一件事、會議形成決定、某人承諾負責、必要欄位仍缺,這四種要成為不同狀態。

這道權限不能偷渡。身分確認只允許寫具名說話者中繼資料;行動候選要保留自己的來源片段與版本,Day 14 再用另一份批准,決定能不能交給假待辦執行器。

參考資料


上一篇
Day 11|時間窗找到了,怎麼確認「這是誰說的」?
系列文
情報進來,內容發出去:30 天拆一條每天真的在跑的 AI 內容產線12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言